﻿namespace EFS.RC.CC.Tools.Recorders
{
	/// <summary>
	/// Recorder
	/// </summary>
	internal class Recorder : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		// sync access
		private object syncAccess = new object();

		/// Customer conference
		private EFS.RC.CC.Confereces.OfficeService officeServiceConference = null;

		// Player conversation
		private Microsoft.Rtc.Collaboration.Conversation conversation = null;

		// Player call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall audioVideoCall = null;

		/// Recorder
		private Microsoft.Rtc.Collaboration.AudioVideo.Recorder recorder = null;

		// uri
		private System.String inpersonatedUri = null;

		// Paused time
		private System.DateTime? pausedSince = null;

		/// <summary>
		/// Status
		/// </summary>
		public enum RecorderStatus { Idle, Started, Terminated }

		// Status
		private RecorderStatus status = RecorderStatus.Idle;

		#endregion

		#region Delegates

		/// <summary>
		/// Asynk terminate 
		/// </summary>
		public EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Recorders.Recorder> AsinkTerminatedDelegate { get; private set; }

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="conversation"></param>
		/// <param name="remoteEndpoint"></param>
		public Recorder(EFS.RC.CC.Confereces.OfficeService officeServiceConference)
		{
			// check
			EFS.Common.Verify.Reference(officeServiceConference, "officeServiceConference");

			// delegate
			AsinkTerminatedDelegate = new EFS.Common.Async.AsyncDelegate<Recorder>(this);

			// make new
			this.officeServiceConference = officeServiceConference;

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceConference.CustomerSession.ConversationGuid, "Recorder:ctor", this);

			// register for terminated events
			officeServiceConference.AsinkTerminatedDelegate.Register(OnOfficeServiceConferenceTerminated);

			// log
			EFS.EventLog.Log.LogInfo("Recorder", this);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Tools.Recorders.Recorder [{0}]", officeServiceConference.Describe());
		}

		/// <summary>
		/// Call
		/// </summary>
		public System.String InpersonatedUri
		{
			get { return inpersonatedUri; }
		}

		/// <summary>
		/// Paused since
		/// </summary>
		public System.DateTime? PausedSice
		{
			get { return pausedSince; }
		}

		/// <summary>
		/// Check paused
		/// </summary>
		public System.Boolean IsPaused
		{
			get
			{
				// lock
				lock (syncAccess)
				{
					// check paused
					return null != pausedSince;	
				}
			}
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Startup, OnStartupComplete);
		}

		/// <summary>
		/// Begin pause
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginPause()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginPause", this);

			// start
			return BeginAsyncProcess2(Pause);
		}

		/// <summary>
		/// Begin resume
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginResume()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginResume", this);

			// start
			return BeginAsyncProcess2(Resume);
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginShutdown", this);

			// start
			return BeginAsyncProcess2(Shutdown, OnShutdownComplete);
		}

		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		private void Startup()
		{
			// log
			EFS.EventLog.Log.LogInfo("Startup", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != RecorderStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle Recorder can be Started");
				}

				// started
				status = RecorderStatus.Started;
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceConference.CustomerSession.ConversationGuid, "Startup", this);

			// join
			BeginAsyncProcess2(JoinOfficeConference);
		}

		/// <summary>
		/// Join office conference
		/// </summary>
		private void JoinOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// get conference uri
			System.String conferenceUri = officeServiceConference.ConferenceUri;

			// get end point
			Microsoft.Rtc.Collaboration.ApplicationEndpoint applicationEndpoint = officeServiceConference.CustomerSession.CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != RecorderStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// make an uri to impersonate into the back conversation
				inpersonatedUri = System.String.Format("sip:{0}@{1}", System.Guid.NewGuid(), applicationEndpoint.DefaultDomain);
				System.String inpersonateDisplayName = "Recorder Tool";

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate conversation {0}", inpersonatedUri), this);

				// make office conversation
				conversation = new Microsoft.Rtc.Collaboration.Conversation(applicationEndpoint);
				conversation.Impersonate(inpersonatedUri, null, inpersonateDisplayName);
				conversation.ApplicationContext = this;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.TrustedParticipant;

				// establish
				conversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeServiceConference));
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeServiceConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeServiceConference", this);

			try
			{
				// try 
				conversation.ConferenceSession.EndJoin(ar);
				
				// call 
				BeginAsyncProcess2(CallOfficeConference);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Call office conference
		/// </summary>
		private void CallOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("CallOfficeConference", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != RecorderStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// call to make audio
				audioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(conversation);

				// remove from default routing
				Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions audioVideoCallEstablishOptions = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions();

				// establish
				audioVideoCall.BeginEstablish(
					audioVideoCallEstablishOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(OnCallEstablished));
			}
		}

		/// <summary>
		/// Call establish result
		/// </summary>
		/// <param name="result"></param>
		private void OnCallEstablished(System.IAsyncResult result)
		{
			try
			{
				// check
				audioVideoCall.EndEstablish(result);
				
				// init
				BeginAsyncProcess2(InitRecorder);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Init recorder
		/// </summary>
		private void InitRecorder()
		{
			// log
			EFS.EventLog.Log.LogInfo("InitRecorder", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != RecorderStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// make recorder
				recorder = new Microsoft.Rtc.Collaboration.AudioVideo.Recorder();

				// attach to call
				recorder.AttachFlow(audioVideoCall.Flow);

				// make path
				System.String directoryPath = System.String.Format("{0}/{1}/{2}/{3}",
					EFS.RC.CC.Config.Settings.RecorderPath,
					officeServiceConference.CustomerSession.StartTime.Year.ToString("00"),
					officeServiceConference.CustomerSession.StartTime.Month.ToString("00"),
					officeServiceConference.CustomerSession.StartTime.Day.ToString("00"));

				// check exists
				if (!System.IO.Directory.Exists(directoryPath))
				{
					// create
					System.IO.Directory.CreateDirectory(directoryPath);
				}

				// wma file 
				Microsoft.Rtc.Collaboration.AudioVideo.WmaFileSink sink = new Microsoft.Rtc.Collaboration.AudioVideo.WmaFileSink(
					System.String.Format("{0}/{1}.wma", directoryPath, officeServiceConference.CustomerSession.ConversationGuid.ToString()));

				// format
				sink.EncodingFormat = Microsoft.Rtc.Collaboration.AudioVideo.WmaEncodingFormat.Pcm8Khz;

				// set sink
				recorder.SetSink(sink);

				// start
				recorder.Start();
			}
		}

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Pause
		/// </summary>
		private void Pause()
		{
			// log
			EFS.EventLog.Log.LogInfo("Pause", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != RecorderStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null == recorder)
				{
					throw new System.InvalidOperationException("Recorder not Initialized");
				}

				// check started
				if (null == pausedSince)
				{
					// CHL
					EFS.RC.CC.Tools.Loggers.CHL.Add(officeServiceConference.CustomerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Recorder_Paused);

					// SAL
					EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceConference.CustomerSession.ConversationGuid, "Pause", this);

					// pause
					BeginAsyncProcess2(RemoveAudioRoutes);

					// start scheduler
					EFS.RC.CC.Tools.Schedulers.ResumeRecorder resumeRecorder = new EFS.RC.CC.Tools.Schedulers.ResumeRecorder(this);
					resumeRecorder.Start();
				}
			}
		}

		/// <summary>
		/// Resume
		/// </summary>
		private void Resume()
		{
			// log
			EFS.EventLog.Log.LogInfo("Resume", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != RecorderStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// check
				if (null == recorder)
				{
					throw new System.InvalidOperationException("Recorder not Initialized");
				}

				// check paused
				if (null != pausedSince)
				{
					// CHL
					EFS.RC.CC.Tools.Loggers.CHL.Add(officeServiceConference.CustomerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Recorder_Resumed);

					// SAL
					EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceConference.CustomerSession.ConversationGuid, "Resume", this);

					// pause
					BeginAsyncProcess2(AddAudioRoutes);
				}
			}
		}

		/// <summary>
		/// Remove audio routes
		/// </summary>
		private void RemoveAudioRoutes()
		{
			EFS.EventLog.Log.LogInfo("RemoveAudioRoutes", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != RecorderStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// get end point
				Microsoft.Rtc.Collaboration.ParticipantEndpoint participantEndpoint = officeServiceConference.GetEndPoint(inpersonatedUri);
				EFS.Common.Verify.Reference(participantEndpoint, "participantEndpoint");

				// remove from default routing
				audioVideoCall.Conversation.ConferenceSession.AudioVideoMcuSession.BeginRemoveFromDefaultRouting(participantEndpoint, OnEndRemoveFromDefaultRouting, null);
			}
		}

		/// <summary>
		/// End remove
		/// </summary>
		/// <param name="result"></param>
		private void OnEndRemoveFromDefaultRouting(System.IAsyncResult result)
		{
			try
			{
				// check
				audioVideoCall.Conversation.ConferenceSession.AudioVideoMcuSession.EndRemoveFromDefaultRouting(result);

				// init
				pausedSince = System.DateTime.Now;
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Add audio routes
		/// </summary>
		private void AddAudioRoutes()
		{
			EFS.EventLog.Log.LogInfo("AddAudioRoutes", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != RecorderStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// get end point
				Microsoft.Rtc.Collaboration.ParticipantEndpoint participantEndpoint = officeServiceConference.GetEndPoint(inpersonatedUri);
				EFS.Common.Verify.Reference(participantEndpoint, "participantEndpoint");

				// remove from default routing
				audioVideoCall.Conversation.ConferenceSession.AudioVideoMcuSession.BeginAddToDefaultRouting(participantEndpoint, OnEndAddToDefaultRouting, null);
			}
		}

		/// <summary>
		/// End add
		/// </summary>
		/// <param name="result"></param>
		private void OnEndAddToDefaultRouting(System.IAsyncResult result)
		{
			try
			{
				// check
				audioVideoCall.Conversation.ConferenceSession.AudioVideoMcuSession.EndAddToDefaultRouting(result);

				// init
				pausedSince = null;
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnException", this);

			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(officeServiceConference.CustomerSession.ConversationGuid, "OnException", this);

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Customer session terminated
		/// </summary>
		/// <param name="playerHunter"></param>
		private void OnOfficeServiceConferenceTerminated(EFS.RC.CC.Confereces.OfficeService officeServiceConference)
		{
			// check
			EFS.Common.Verify.Reference(officeServiceConference, "officeServiceConference");

			// check mine
			EFS.Common.Verify.Condition(this.officeServiceConference.Equals(officeServiceConference), "Terminated OfficeService Customers Confereces is different");

			// shut down
			BeginShutdown();
		}


		#endregion

		#region Shutdown

		/// <summary>
		/// Shut down
		/// </summary>
		public void Shutdown()
		{
			EFS.EventLog.Log.LogInfo("Shutdown", this);

			// lock local acess
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check already terminated
				if (RecorderStatus.Terminated == status)
				{
					return;
				}

				// set terinated
				status = RecorderStatus.Terminated;
			}

			// Session is terminated, notify 
			AsinkTerminatedDelegate.Start();

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// terminate all
			termination.Stop(recorder);
			termination.Terminate(conversation);
		}

		/// <summary>
		/// Shut down complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnShutdownComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnShutdownComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnShutdownComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		#endregion
	}
}
